#include <QtGui>
#include <vector>
#include <algorithm>
#include <time.h>
#include "word.h"
#include "globalshortcutmanager.h"
#include "verbRunner.h"


void Word::entered() {
  if (((InputWin*)parentWidget())->db.isOpen() && hasSelection() && (this->type == VERB || currentSelectionType() != TEXTITEM)) {
    sqlQ.prepare("UPDATE " + DBTABLE + " SET freq = freq+1, lastCall = strftime('%s','now') WHERE path = :path");
    sqlQ.bindValue(":path", QString::fromStdString(getSelectedPath()));
    sqlQ.exec();
    if (sqlQ.numRowsAffected() < 1) {
      sqlQ.prepare("INSERT INTO " + DBTABLE + " (path, keyword) VALUES (:path, :keyword)");
      sqlQ.bindValue(":path", QString::fromStdString(getSelectedPath()));
      sqlQ.bindValue(":keyword", query->text());
      sqlQ.exec();
    }
  }
}

/* edited: 
  Call to refresh the results according to text in query (even without argument)
  Uses text in query (not the "input" parameter) for search
*/
void Word::edited(const QString& input, bool reload) {
  // On filtering input, reset directory back to default
  if (input.length() > 0 && makeRecurMode) {
    // Reset modes
    makeRecurMode = false;
    browseMode = false;
    reload = true;
    fileBrowser.cd(defDir);
  }
  // Recursive listing if word is verb or not in browseMode
  bool recursive = !browseMode || this->type == VERB;
  if (reload || curFileList.length() == 0) {
    curFileList = getFileList(recursive);
  }
  // Filter down from previous results if not explicitly told to reload AND input increased;
  // else, score from whole file list
  bool filterDown = !reload && (input.length() > oldQuery.length());
  oldQuery = query->text();
  addResults(query->text(), filterDown);
}

void Word::changeDirectory(int cdType) {
  // Change to browsing mode
  browseMode = true;
  makeRecurMode = false;
  query->clear();
  if (cdType == CDHOME) {
    fileBrowser.cd(QDir::homePath());
  // If an item is selected, change directory based on the item
  } else if (currentSelectionType() == FILEITEM) {
    QString dir;
    if (cdType == CDUP) {
      dir = QFileInfo(QFileInfo(QString::fromStdString(getSelectedPath())).dir().absolutePath()).dir().absolutePath();
    } else if (cdType == CDDOWN) {
      dir = QString::fromStdString(getSelectedPath());
    }
    fileBrowser.cd(dir);
  } else if (cdType == CDUP) {
    fileBrowser.cdUp();
  }
  edited("", true);
}

void Word::changeSelection(bool backwards) {
  if (hasSelection()) {
    // Reset making recursive mode
    makeRecurMode = false;
    int diff = 0;
    if (backwards) {
      if (resultsBox->currentRow() > 0) diff = -1;
      else diff = resultsBox->count()-1;
    } else {
      if (resultsBox->currentRow() < resultsBox->count()-1) diff = 1;
      else diff = 1-resultsBox->count();
    }
    resultsBox->setCurrentRow(resultsBox->currentRow()+diff);
  }
}

QStringList Word::getFileList(bool recur) {
  QStringList fileList;
  // Listing files recursively
  if (recur) {
    const QString curPath = fileBrowser.absolutePath();
    QStringList dirList = recurBrowse(curPath, 1);
    dirList.insert(0, curPath);
    for (int i=0; i<dirList.length(); i++) {
      QDir directory(dirList[i]);
      directory.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
      QStringList tempFileList = directory.entryList();
      QString dir = directory.absolutePath() + QString::fromStdString("/");
      for (int i=0; i<tempFileList.length(); i++) {
        tempFileList[i] = dir + tempFileList[i];
      }
      fileList.append(tempFileList);
    }
  // Listing files in just current directory
  } else {
    fileList = fileBrowser.entryList();
    QString dir = fileBrowser.absolutePath() + QString::fromStdString("/");
    for (int i=0; i<fileList.length(); i++) {
      fileList[i] = dir + fileList[i];
    }
  }
  return fileList;
}

QStringList Word::recurBrowse(QString dir, int iter) {
  if (iter > 0) {
    QDir directory(dir);
    QStringList dirList = directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
    QString dirPath = directory.absolutePath() + QString::fromStdString("/");
    for (int i=0; i<dirList.length(); i++) {
      dirList[i] = dirPath + dirList[i];
    }
    QStringList dirBuffer;
    for (int i=0; i<dirList.length(); i++) {
      dirBuffer.append(recurBrowse(dirList[i], iter-1));
    }
    dirList.append(dirBuffer);
    return dirList;
  }
  return QStringList();
}

void Word::addResults(QString searchString, bool filterDown) {
  QList<ResultItem> resultsList;
  if (filterDown) {
    QStringList filteredFileList;
    for (int i=0; i<resultsBox->count(); i++) {
      filteredFileList.append(QString::fromStdString(((ResultItem*)resultsBox->item(i))->path));
    }
    resultsList = scoreFiles(filteredFileList, searchString);
  } else {
    resultsList = scoreFiles(curFileList, searchString);
  }
  // Clear results for now; TODO: update results by adding/removing to improve performance
  resultsBox->clear();
  // Create and add results
  for (int i=0; i<resultsList.length(); i++) {
    bool filtered = false;
    switch (type) {
      case VERB: if ( !(typeFilter & getVerbSubType(resultsList[i].path)) ) {
                   filtered = true;
                 }
                 break;
    }
    ResultItem * temp = new ResultItem(resultsList[i]);
    if (!filtered) {
      if (this->type != VERB) {
        temp->type = FILEITEM;
      // Push up "open" command
      } else if (temp->text() == QString::fromStdString("open") && searchString.length() == 0) {
        temp->score = 99999;
      }
      temp->setSizeHint(QSize(0, query->height())); //styling
      resultsBox->addItem(temp);
    }
  }
  // Adding literal item ("text")
  if (this->type != VERB && searchString.length() > 0) {
    ResultItem * temp = new ResultItem("\"" + searchString.toStdString() + "\"");
    temp->path = searchString.toStdString();
    temp->score = -1;
    temp->type = TEXTITEM;
    temp->setSizeHint(QSize(0, query->height())); //styling
    resultsBox->addItem(temp);
  }
  // Refresh list / default selection
  if (resultsBox->count() == 0) {
    resultsBox->update();
  } else {
    resultsBox->sortItems(Qt::DescendingOrder);
    resultsBox->setCurrentRow(0);
  }
}

void Word::newSelection(QListWidgetItem * cur, QListWidgetItem * prev) {
  if (cur) {
    int row = resultsBox->currentRow();
    cur->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
    status->setText(QString::fromStdString(((ResultItem*)cur)->path));
    if (prev) {
      prev->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
    }
    for (int i=-5; i<6 && i+row<resultsBox->count(); i++) {
      if (i+row>=0 && i+row<resultsBox->count() && resultsBox->item(i+row)->icon().isNull()) {
        emit this->itemApproached((ResultItem*)resultsBox->item(i+row));
      }
    }
    emit this->newSelected();
  }
}

void Word::loadIcon(ResultItem * item) {
  if (item->type == 1) {
    QString filePath = QString::fromStdString(item->path);
    QIcon icon;
    if (QImageReader(filePath).canRead()) {
      icon = QIcon(filePath);
    } else {
      icon = QFileIconProvider().icon(QFileInfo(filePath));
    }
    item->setIcon(icon);
  }
}

Word::Word(InputWin *parent, int pType) : QWidget((QWidget*)parent) {
  
  type = pType;
  
  switch (type) {
    case NOUN: defDir = settings.value("favDir").toString();
               DBTABLE = "nHistory";
               break;
    case AUX: defDir = settings.value("favDir").toString();
              DBTABLE = "aHistory";
              break;
    case VERB: defDir = settings.value("verbsDir").toString();
               DBTABLE = "vHistory";
               break;
  }
  fileBrowser = QDir(defDir);
  fileBrowser.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
  
  browseMode = false;
  
  query = new Query(this);
  resultsBox = new QListWidget(this);
  status = new QLabel(this);
  resultsBox->setFocusProxy(query);
  setFocusProxy(query);
  
  connect(query, SIGNAL(textEdited(const QString&)), this, SLOT(edited(const QString&)));
  connect(parent, SIGNAL(enter()), this, SLOT(entered()));
  connect(query, SIGNAL(returnPressed()), parent, SLOT(entered()));
  connect(query, SIGNAL(specialKeyPressed(QKeyEvent *)), this, SLOT(handleKey(QKeyEvent *)));
  connect(resultsBox, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(newSelection(QListWidgetItem *, QListWidgetItem *)));
  connect(this, SIGNAL(itemApproached(ResultItem *)), this, SLOT(loadIcon(ResultItem *)), Qt::QueuedConnection);
  
  // styling ///////////
  QRect space = QApplication::desktop()->availableGeometry();
  query->resize(parentWidget()->width()/3.0, parentWidget()->height()*0.15);
  resultsBox->resize(query->width(), parentWidget()->height()*0.8);
  resultsBox->move(0, query->height());
  resultsBox->setIconSize(QSize(query->height(), query->height()));
  status->resize(query->width(), parentWidget()->height()*0.05);
  status->move(0, query->height()+resultsBox->height());
  
  QFont statFont("Helvetica", status->height()*.64, QFont::Normal);
  status->setFont(statFont);
  QPalette statPal = QPalette();
  statPal.setColor(QPalette::Window, QColor(50, 50, 50, 150));
  statPal.setColor(QPalette::WindowText, QColor(255, 255, 255, 255));
  status->setAutoFillBackground(true);
  status->setPalette(statPal);
  
  QFont uiFont("Helvetica", query->height()*.64, QFont::Bold);
  setFont(uiFont);
  query->setFrame(false);
  QPalette pal = QPalette();
  pal.setColor(QPalette::Base, QColor(50, 50, 50, 150));
  pal.setColor(QPalette::Text, QColor(255, 255, 255, 150));
  pal.setColor(QPalette::Highlight, QColor(0, 0, 0, 200));
  pal.setColor(QPalette::HighlightedText, QColor(255, 255, 255));
  this->setPalette(pal);
  query->setPalette(pal);
  //resultsBox->setPalette(pal);
  
  edited("", true);
}

string Word::getSelectedPath() {
  return ((ResultItem*)resultsBox->currentItem())->path;
}

int Word::getVerbSubType(string path) {
  string cmd = "\"" + path + "\" -t";
  return verbRunner::run(cmd).toInt();
}

QList<ResultItem> Word::scoreFiles(QStringList list, QString match) { // Sends info to scorer and adds parent directory to results
  // Remove nonverbs from verb
  if (this->type == VERB) {
    for (int i=list.length()-1; i>=0; i--) {
      if (!isVerb(list[i].toStdString())) {
        list.removeAt(i);
      }
    }
  }
  // Ignore extension in scoring if verb
  QList<ResultItem> resultsList = scorer::score(QStringListToVector(list), match.toStdString(), this->type == VERB);
  int freqBonus = settings.value("freqBonus").toInt();
  int recencyHigh = settings.value("recencyHigh").toInt();
  // frecency
  for (int i=0; i<(int)resultsList.size(); i++) {
    sqlQ.prepare("SELECT freq, lastCall FROM " + DBTABLE + " WHERE path = :path");
    sqlQ.bindValue(":path", QString::fromStdString(resultsList[i].path));
    sqlQ.exec();
    if (sqlQ.size() > 1) qDebug()<<"Something went wrong -- more than one identical path"<<sqlQ.result();
    else if (sqlQ.next()) {
      int freqScore = log(exp(1)*sqlQ.value(0).toInt())*freqBonus; // ln(ex)*freqBonus; put freq count on log scale to lessen effects of large differences
      int recencyScore = recencyHigh/sqrt( 1 + (time(NULL)-sqlQ.value(1).toInt())/120 ); // recencyHigh/sqrt(1 + time difference/2minutes), score changes every 2 minutes; (+1 to prevent infinity)
      resultsList[i].score += freqScore + recencyScore;
    }
  }
  return resultsList;
}

vector<string> Word::QStringListToVector(const QStringList list) {
  vector<string> out(0);
  for (int i=0; i<list.length(); i++) {
    out.insert(out.end(), list[i].toStdString());
  }
  return out;
}

bool Word::isVerb(string path) {
  QFileInfo file(QString::fromStdString(path));
  if (file.suffix() == "verb") {
    return true;
  } else {
    return false;
  }
}

string Word::getVerbName(string path) {
  QFileInfo file(QString::fromStdString(path));
  return file.completeBaseName().toStdString();
}

bool Word::hasSelection() {
  if (resultsBox->currentRow() >= 0 && resultsBox->currentRow() < resultsBox->count()) {
    return true;
  } else {
    return false;
  }
}

int Word::currentSelectionType() {
  if (hasSelection()) {
    return ((ResultItem*)resultsBox->currentItem())->type;
  } else {
    return -1;
  }
}

void Word::handleKey(QKeyEvent * e) {
  // Enable special keys only if text item not selected
  if (currentSelectionType() != TEXTITEM) {
    switch(e->key()) {
      case Qt::Key_Slash: changeDirectory(CDDOWN);
                          break;
      case Qt::Key_Question: changeDirectory(CDUP);
                             break;
      case Qt::Key_AsciiTilde: changeDirectory(CDHOME);
                               break;
      case Qt::Key_Space: if (currentSelectionType() != TEXTITEM) {
                            changeSelection(e->modifiers() & Qt::ShiftModifier);
                          }
                          break;
      default: QWidget::keyPressEvent(e);
               break;
    }
  } else {
    query->insert(e->text());
  }
}
